iT邦幫忙

2024 iThome 鐵人賽

DAY 19
0
Mobile Development

用 SwiftUI 掌控家庭日用品庫存系列 第 19

Day19: SwiftUI 分類管理、地點管理與側邊欄結合,提升物品管理功能

  • 分享至 

  • xImage
  •  

我們今天要來把前幾天寫的管理分類和管理地點與側邊欄結合,並且對 Item 模型進行修改,讓它能與分類和地點進行關聯。這樣,使用者在新增或修改物品時,能夠指定物品的分類和地點。在首頁查看物品時,也可以顯示該物品的分類圖示與地點,讓管理更加直覺、方便。

目標

今天預計要實作以下功能:

  • 將分類管理、地點管理功能與側邊欄結合,讓使用者點選側邊欄的選項可以使用這些功能。
  • 修改 Item 模型,增加與分類和地點的關聯。

準備好了嗎?開始囉~

整合側邊欄

我們在 Day14 實作側邊欄的時候,為了要快速顯示所有的側邊欄按鈕,所以使用一個自定義結構 MenuItem 來儲存側邊欄的功能,並且使用 ForEachButton 來產生每一顆按鈕。

ForEach(items) { item in
    HStack {
        Button(action: {
            
        }, label: {
            Image(systemName: item.icon)
            Text(item.text)
        })
        .foregroundColor(Color("FontColor"))
        .frame(maxWidth: .infinity)
        .font(.headline)
        .padding()
        .overlay {
            RoundedRectangle(cornerRadius: 15)
                .stroke(.gray, lineWidth: 2)
        }
        
        
        Spacer()
    }
    .padding(8)
}

現在我們來改寫這段程式碼,有兩種方法可以處理側邊欄按鈕的顯示:

方法一:保留 MenuItem

修改 MenuItem

原本的 MenuItem 結構只有 id、text 和 icon,現在我們加入 destination 來指定按鈕跳轉的頁面。

struct MenuItem: Identifiable {
    var id = UUID()
    let text: String
    let icon: String
    let destination: AnyView
}

AnyView 概念
AnyView 是 SwiftUI 中用來封裝不同型別 View 的一種工具。當我們需要在同一個結構內處理多種不同型別的 View,或動態生成 View 時,AnyView 允許將這些不同的 View 封裝成統一的型別來處理。由於 SwiftUI 的型別系統要求每個 View 必須有明確的型別,AnyView 提供了靈活性來處理動態或條件性顯示的 View。

使用 AnyView 可能會帶來一些效能損失,因為它隱藏了具體的型別資訊。為了最佳化效能,應該盡量在只有需要動態處理多型別視圖時使用 AnyView,不能將它作為預設解決方案。

參考資料:

修改 MenuContent

透過改寫 MenuItem,我們可以繼續使用 ForEach 動態生成按鈕,避免重複的程式碼。

var body: some View {
    ZStack {
        Color("BgColor")
        
        VStack(alignment: .leading, spacing: 0) {
            ForEach(items) { item in
                HStack {
                    NavigationLink(destination: item.destination) {
                        HStack {
                            Image(systemName: item.icon)
                            Text(item.text)
                        }
                        .foregroundColor(Color("FontColor"))
                        .frame(maxWidth: .infinity)
                        .font(.headline)
                        .padding()
                        .overlay {
                            RoundedRectangle(cornerRadius: 15)
                                .stroke(.gray, lineWidth: 2)
                        }
                    }
                        
                    Spacer()
                }
                .padding(8)
            }
            
            Spacer()
        }
    }
}

這種方法適合所有按鈕都需要跳轉頁面的情況。不過,像「給予評分」和「聯絡我們」這些按鈕通常不會跳轉頁面,這時我們可以採用第二種方法。

方法二:捨棄 MenuItem 結構

捨棄 MenuItem 結構,以手動的方式建立按鈕,為了避免重複性程式碼太多,我把可以元件化的程式抽出來處理,因此我們刪除 MenuItem,並建立新的 MenuButton。

MenuButton

為了避免重複性程式碼,我們將按鈕樣式抽離成元件,這樣可以簡化程式碼。

struct MenuButton: View {
    let title: String
    let icon: String
    
    var body: some View {
        HStack {
            Image(systemName: icon)
            Text(title)
        }
        .foregroundColor(Color("FontColor"))
        .frame(maxWidth: .infinity)
        .font(.headline)
        .padding()
        .overlay {
            RoundedRectangle(cornerRadius: 15)
                .stroke(.gray, lineWidth: 2)
        }
    }
}

修改 MenuContent

移除掉原本的程式碼,改用一個一個按鈕手動建立的方式,在中間需要按鈕樣式的地方呼叫剛剛寫好的 MenuButton,這樣可以手動控制每個按鈕的行為。

var body: some View {
    ZStack {
        Color("BgColor")
        
        VStack(alignment: .leading, spacing: 0) {
            NavigationLink(destination: CategoryListView()) {
                MenuButton(title: "管理分類", icon: "square.grid.2x2")
            }
            .padding(8)
            
            NavigationLink(destination: LocationListView()) {
                MenuButton(title: "管理地點", icon: "location")
            }
            .padding(8)
            
            NavigationLink(destination: LocationListView()) {
                MenuButton(title: "帳務報表", icon: "chart.pie")
            }  // 因為還沒建立好報表的頁面,先用 LocationListView 代替一下
            .padding(8)
            
            Button(action: {
                
            }, label: {
                MenuButton(title: "給予評分", icon: "star.bubble")
            })
            .padding(8)
            
            Button(action: {
                
            }, label: {
                MenuButton(title: "與我聯絡", icon: "envelope")
            })
            .padding(8)
            
            Spacer()
        }
    }
}

修改 Item 模型

接下來,我們對 Item 模型進行修改,讓每個物品都能關聯到一個分類和一個地點。這樣使用者在新增或修改物品時,可以選擇物品的分類和存放地點,讓物品管理更加清晰。

平常我們都是在修改 Attributes 欄位,現在我們需要在 Relationships 新增 category 關聯 ItemCategory、location 關聯 Location。

https://ooorito.com/wp-content/uploads/2024/09/%E6%9B%B4%E6%96%B0item%E6%A8%A1%E5%9E%8B-1024x642.webp

別忘了更新程式碼:

extension Item {

    @nonobjc public class func fetchRequest() -> NSFetchRequest<Item> {
        return NSFetchRequest<Item>(entityName: "Item")
    }

    @NSManaged public var dateAdded: Date?
    @NSManaged public var expiryDate: Date?
    @NSManaged public var id: UUID?
    @NSManaged public var isUsedUp: Bool
    @NSManaged public var name: String
    @NSManaged public var price: Double
    @NSManaged public var quantity: Int16
    @NSManaged public var usedQuantity: Int16
    @NSManaged public var category: ItemCategory
    @NSManaged public var location: Location

}

我們新增了 category 和 location 屬性,用來儲存物品所屬的分類和存放地點。每當使用者在新增或修改物品時,可以選擇這兩個屬性。

總結

今天我們將分類與地點管理功能整合到側邊欄,並且更新了 Item 模型,讓物品能夠與分類和地點進行關聯。這些改進將為接下來的功能實作打下堅實的基礎。明天,我們將優化新增與編輯物品的頁面,並將分類和地點選項整合進去。明天見!


上一篇
Day18: SwiftUI 地點管理功能實作
下一篇
Day 20: SwiftUI 優化新增與編輯物品頁面
系列文
用 SwiftUI 掌控家庭日用品庫存30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言